#include "Model3D.h"
#include "KDTree.h"

// Model3D

Model3D::Model3D() : mp_kdtree(nullptr)
  {
  }

Model3D::Model3D(std::string i_material_name)
  : m_material_name(i_material_name)
  , mp_kdtree(nullptr)
  {
  }

Model3D::~Model3D()
  {
  for(auto it = m_points.begin(); it != m_points.end(); it++)
    {
    delete *it;
    }

  for(auto it = m_triangles.begin(); it != m_triangles.end(); it++)
    {
    delete *it;
    }

  if (mp_kdtree != nullptr)
    {
    delete mp_kdtree;
    }
  }

void Model3D::Clear()
  {
  for(auto it : m_points)
    delete it;

  for(auto it : m_triangles)
    delete it;

  m_points.clear();
  m_triangles.clear();
  }

Point3DID Model3D::AddPoint(double i_x, double i_y, double i_z)
  {
  Point3D *point = new Point3D(i_x, i_y, i_z);
  m_points.push_back(point);

  return Point3DID(m_points.back());
  }

Point3DID Model3D::AddPoint(TVector3 i_point)
  {
  return AddPoint(i_point.X(), i_point.Y(), i_point.Z());
  }

Triangle3DID Model3D::AddTriangle(const Point3DID &i_first, const Point3DID &i_second, const Point3DID &i_third)
  {
  Triangle3D *triangle = new Triangle3D(i_first, i_second, i_third);
  m_triangles.push_back(triangle);

  i_first.m_object->m_triangles.push_back(triangle);
  i_second.m_object->m_triangles.push_back(triangle);
  i_third.m_object->m_triangles.push_back(triangle);

  if (mp_kdtree != nullptr)
    {
    delete mp_kdtree;
    mp_kdtree = nullptr;
    }
  return Triangle3DID(triangle);
  }


std::vector<TTriangle3> Model3D::PickTriangles(const TLine3& i_line) const
  {
  if (mp_kdtree == nullptr)
    mp_kdtree = new KDTree (this->Triangles().begin(), this->Triangles().end());
  auto triangles = mp_kdtree->PickTriangles(i_line);
  std::vector <TTriangle3> result;
  for (auto it : triangles)
    {
    TTriangle3 current_triangle;
    current_triangle.V[0] = it->GetPoint(0);
    current_triangle.V[1] = it->GetPoint(1);
    current_triangle.V[2] = it->GetPoint(2);
    result.push_back(current_triangle);
    }
  return result;
  }

Model3D::PointsIteratorGetter Model3D::Points() const
  {
  PointsIteratorGetter getter(m_points);
  return getter;
  }

Model3D::TrianglesIteratorConstGetter Model3D::Triangles() const
  {
  TrianglesIteratorConstGetter getter(m_triangles);
  return getter;
  }

Model3D::TrianglesIteratorGetter Model3D::TrianglesMutable()
  {
  TrianglesIteratorGetter getter(m_triangles);
  return getter;
  }

std::string Model3D::GetMaterialName() const
  {
  return m_material_name;
  }

void Model3D::SetMaterialName(std::string i_material_name)
  {
  m_material_name = i_material_name;
  }

void Model3D::ScaleTransform(double i_coef)
  {
  for (auto it = m_points.begin(); it != m_points.end(); ++it)
    {
    (**it).m_data = TVector3((**it).GetX()*i_coef, (**it).GetY()*i_coef, (**it).GetZ()*i_coef);
    }
  }

void Model3D::ScaleTransform(const TVector3& i_coef)
  {
  for (auto it = m_points.begin(); it != m_points.end(); ++it)
    {
    (**it).m_data = TVector3((**it).GetX()*i_coef.X(), (**it).GetY()*i_coef.Y(), (**it).GetZ()*i_coef.Z());
    }
  }

void Model3D::ParalelShift(const TVector3& i_shift)
  {
  for (auto it = m_points.begin(); it != m_points.end(); ++it)
    {
    (**it).m_data = TVector3((**it).GetX() + i_shift.X(), (**it).GetY() + i_shift.Y(), (**it).GetZ() + i_shift.Z());
    }
  }

bool Model3D::ContainsTriangle(const TTriangle3& i_triangle) const
  {                      
  double x0 = i_triangle.V[0].X();
  double y0 = i_triangle.V[0].Y();
  double z0 = i_triangle.V[0].Z();

  double x1 = i_triangle.V[1].X();
  double y1 = i_triangle.V[1].Y();
  double z1 = i_triangle.V[1].Z();

  double x2 = i_triangle.V[2].X();
  double y2 = i_triangle.V[2].Y();
  double z2 = i_triangle.V[2].Z();
  
  for (auto it = Triangles().begin(); it != Triangles().end(); ++it)
    {
    if ( 
      (x0 == it->GetPoint(0).X() && y0 == it->GetPoint(0).Y() && z0 == it->GetPoint(0).Z() &&
        x1 == it->GetPoint(1).X() && y1 == it->GetPoint(1).Y() && z1 == it->GetPoint(1).Z() &&
        x2 == it->GetPoint(2).X() && y2 == it->GetPoint(2).Y() && z2 == it->GetPoint(2).Z()
       ) ||
       (x0 == it->GetPoint(1).X() && y0 == it->GetPoint(1).Y() && z0 == it->GetPoint(1).Z() &&
       x1 == it->GetPoint(2).X() && y1 == it->GetPoint(2).Y() && z1 == it->GetPoint(2).Z() &&
       x2 == it->GetPoint(0).X() && y2 == it->GetPoint(0).Y() && z2 == it->GetPoint(0).Z()
       ) ||
       (x0 == it->GetPoint(2).X() && y0 == it->GetPoint(2).Y() && z0 == it->GetPoint(2).Z() &&
       x1 == it->GetPoint(0).X() && y1 == it->GetPoint(0).Y() && z1 == it->GetPoint(0).Z() &&
       x2 == it->GetPoint(1).X() && y2 == it->GetPoint(1).Y() && z2 == it->GetPoint(1).Z()
       )
       )
       return true;
    }

  return false;
  }

std::vector<const Triangle3D*> Model3D::GetNeighborTriangles(Point3DID i_id) const
  {
  return i_id.m_object->GetTriangles();
  }

std::shared_ptr<Model3D> Model3D::Clone() const
  {
  std::shared_ptr<Model3D> cloned_model(new Model3D());

  for (auto it = this->Triangles().begin(); it != this->Triangles().end(); ++it)
    {
    Point3DID id_1 = cloned_model->AddPoint( (*it).GetPoint(0) );
    Point3DID id_2 = cloned_model->AddPoint( (*it).GetPoint(1) );
    Point3DID id_3 = cloned_model->AddPoint( (*it).GetPoint(2) );
    cloned_model->AddTriangle(id_1, id_2, id_3);
    }

  return cloned_model;
  }

// Iterator getters

Model3D::PointsIteratorGetter::PointsIteratorGetter(const std::list<Point3D*> &i_list)
  :m_list(i_list)
  {
  }

Model3D::TrianglesIteratorConstGetter::TrianglesIteratorConstGetter(const std::list<Triangle3D*> &i_list)
  :m_list(i_list)
  {
  }

Model3D::Point3DIteratorConst Model3D::PointsIteratorGetter::begin()
  {
  Point3DIteratorConst iter(m_list.cbegin());
  return iter;
  }

Model3D::Point3DIteratorConst Model3D::PointsIteratorGetter::end()
  {
  Point3DIteratorConst iter(m_list.cend());
  return iter;
  }

Model3D::Triangle3DIteratorConst Model3D::TrianglesIteratorConstGetter::begin()
  {
  Triangle3DIteratorConst iter(m_list.cbegin());
  return iter;
  }

Model3D::Triangle3DIteratorConst Model3D::TrianglesIteratorConstGetter::end()
  {
  Triangle3DIteratorConst iter(m_list.cend());
  return iter;
  }

// Iterators

Model3D::Point3DIteratorConst::Point3DIteratorConst(const std::list<Point3D*>::const_iterator &i_iter)
  :m_iter(i_iter)
  {
  }

Model3D::Point3DIteratorConst& Model3D::Point3DIteratorConst::operator++()
  {
  m_iter++;
  return *this;
  }

Model3D::Point3DIteratorConst Model3D::Point3DIteratorConst::operator++(int)
  {
  Model3D::Point3DIteratorConst iter(m_iter);
  operator++();

  return iter;
  }

const TVector3& Model3D::Point3DIteratorConst::operator*()
  {
  return (*m_iter)->m_data;
  }

const TVector3* Model3D::Point3DIteratorConst::operator->()
  {
  return &(*m_iter)->m_data;
  }

bool Model3D::Point3DIteratorConst::operator==(const Model3D::Point3DIteratorConst &i_other)
  {
  return m_iter == i_other.m_iter;
  }

bool Model3D::Point3DIteratorConst::operator!=(const Model3D::Point3DIteratorConst &i_other)
  {
  return m_iter != i_other.m_iter;
  }

Model3D::Triangle3DIteratorConst::Triangle3DIteratorConst(const std::list<Triangle3D*>::const_iterator &i_iter)
  :m_iter(i_iter)
  {
  }

Model3D::Triangle3DIteratorConst& Model3D::Triangle3DIteratorConst::operator++()
  {
  m_iter++;
  return *this;
  }

Model3D::Triangle3DIteratorConst Model3D::Triangle3DIteratorConst::operator++(int)
  {
  Model3D::Triangle3DIteratorConst iter(m_iter);
  operator++();

  return iter;
  }

const Triangle3D& Model3D::Triangle3DIteratorConst::operator*() const
  {
  return *(*m_iter);
  }

const Triangle3D* Model3D::Triangle3DIteratorConst::operator->() const
  {
  return *m_iter;
  }

bool Model3D::Triangle3DIteratorConst::operator==(const Model3D::Triangle3DIteratorConst &i_other)
  {
  return m_iter == i_other.m_iter;
  }

bool Model3D::Triangle3DIteratorConst::operator!=(const Model3D::Triangle3DIteratorConst &i_other)
  {
  return m_iter != i_other.m_iter;
  }

typedef Model3D::Triangle3DIterator Triangle3DIterator;
typedef Model3D::TrianglesIteratorGetter TrianglesIteratorGetter;

Model3D::Triangle3DIterator::Triangle3DIterator(const std::list<Triangle3D*>::iterator &i_iter)
  : m_iter(i_iter)
  {

  }

Triangle3DIterator& Model3D::Triangle3DIterator::operator++()
  {
  ++m_iter;
  return *this;
  }

Model3D::Triangle3DIterator Model3D::Triangle3DIterator::operator++(int)
  {
  auto iter = m_iter;
  ++m_iter;
  return *this;
  }

Triangle3D& Model3D::Triangle3DIterator::operator*() const
  {
  return **m_iter;
  }

Triangle3D* Model3D::Triangle3DIterator::operator->() const
  {
  return *m_iter;
  }

bool Model3D::Triangle3DIterator::operator==(const Triangle3DIterator &i_other)
  {
  return m_iter == i_other.m_iter;
  }

bool Model3D::Triangle3DIterator::operator!=(const Triangle3DIterator &i_other)
  {
  return !(*this == i_other);
  }


Model3D::TrianglesIteratorGetter::TrianglesIteratorGetter(std::list<Triangle3D*> &i_list)
  : m_list(i_list)
  {

  }

Triangle3DIterator Model3D::TrianglesIteratorGetter::begin()
  {
  return m_list.begin();
  }

Triangle3DIterator Model3D::TrianglesIteratorGetter::end()
  {
  return m_list.end();
  }
